# This file is part of Gehyra.
#
# Gehyra is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Gehyra is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gehyra.  If not, see <http://www.gnu.org/licenses/>.

"""
@package gehyra.common.cidr
Contains the CIDR class for manipulation of CIDR ranges expressed using both
IPv4 and IPv6.
$Id: cidr.py 458 2011-02-10 12:32:02Z andyhhp@gmail.com $
"""

"""
@file gehyra/common/cidr.py
Contains the CIDR class for manipulation of CIDR ranges expressed using both
IPv4 and IPv6.
"""

from gehyra.common.util import CHECK
from gehyra.common.ipv4 import IPv4Address
from gehyra.common.ipv6 import IPv6Address

class CIDR(object):
    """Class for manipulating CIDR ranges of IP addresses
    Can deal with dotted decimal representation, packed structures
    and integers in string or int form, and can deal with both IPv4 and v6.
    """

    def __init__(self, param1, param2 = None):
        """Constructor.
        Can take several possible representations of a CIDR range, for both
        IPv4 and IPv6.
        @param param1 Can be:
         - String in CIDR notation with 'IP/prefix' notation
         - Two-Tuple containing a String recognised by IPvXAddress and an
             integer prefix
         - IPvXAddress object
         - String recognised by the constructor to IPvXAddress
        @param param2 Defaults to None.  In the case where param1 specifies 
        an IP address only, param2 should be the prefix to make the CIDR Range
        @exception TypeError if params are not valid types for turning into a
        CIDR range.
        @exception ValueError if params are valid types but no within range
        """

        ## IPv4Address or IPv6Address object
        self.addr = None
        ## integer prefix for the CIDR Range
        self.prefix = None
        ## Internet Protocol Version (affect valid prefixes)
        self._version = None
        ## Bit mask generated from prefix for compairing ranges
        self._mask = None

        if isinstance(param1, tuple):
            if len(param1) != 2:
                raise ValueError("Tuple should have 2 elements")

            if not isinstance(param1[0], (str, IPv4Address, IPv6Address)):
                raise TypeError("First element of the tuple should be a "
                                "string or an IPvXAddress object")
            self._set_address(param1[0])

            if not isinstance(param1[1], (int, long)):
                raise TypeError("Second element of the tuple should be an "
                                "integer")

            self._set_prefix(param1[1])

            if param2 is not None:
                raise TypeError("Not expecting second parameter with tuple")

        elif isinstance(param1, IPv4Address):
            self._set_addr4(param1)
            if not isinstance(param2, (int, long)):
                raise TypeError("Second parameter should be an integer")
            self._set_prefix(param2)


        elif isinstance(param1, IPv6Address):
            self._set_addr6(param1)
            if not isinstance(param2, (int, long)):
                raise TypeError("Second parameter should be an integer")
            self._set_prefix(param2)

        elif isinstance(param1, str):

            if param1.find('/') == -1:
                self._set_address(param1)
                if not isinstance(param2, (int, long)):
                    raise TypeError("Second parameter should be an integer")
                self._set_prefix(param2)

            else:
                a, m = param1.split('/', 1)
                self._set_address(a)
                try:
                    self._set_prefix(int(m))
                except ValueError:
                    raise ValueError("Expected integer after '/' in CIDR "
                                     "string")

                if param2 is not None:
                    raise TypeError("Not expecting a second parameter with "
                                    "CIDR string")

        else:
            raise TypeError("Expected a string, tuple or IPvNAddress object "
                            "as the first parameter")


        # these really should never trigger
        if self.addr is None:
            raise RuntimeError("Parameters do not contain an IP address")
        if self.prefix is None:
            raise RuntimeError("Parameters do not contain a prefix")

        CHECK(self._version == 4 or self._version == 6)

    def _set_address(self, addr):
        """Private helper to set the address from an object containing just IP
        address"""

        if isinstance(addr, IPv4Address):
            self._set_addr4(addr)
        elif isinstance(addr, IPv6Address):
            self._set_addr6(addr)
        else:
            try:
                self._set_addr4(IPv4Address(addr))
            except ValueError:
                try:
                    self._set_addr6(IPv6Address(addr))
                except ValueError:
                    raise ValueError("String '%s' cannot be interpreted "
                                     "as a valid IPv4 or 6 address")
        

    def _set_addr4(self, addr):
        """Private helper to set the address from an IPv4Address object"""
        self.addr = addr
        self._version = 4

    def _set_addr6(self, addr):
        """Private helper to set the address from an IPv6Address object"""
        self.addr = addr
        self._version = 6

    def _set_prefix(self, prefix):
        """Private helper to set the prefix and generate a mask"""
        CHECK(self._version == 4 or self._version == 6)

        if self._version == 4:
            if not (0 <= prefix <= 32):
                raise ValueError("Prefixs for IPv4 must be in the range "
                                 "0 <= val <= 32.  Got %d" % prefix)
            if prefix == 0:
                mask = 0
            else:
                mask = ~0 << (32 - prefix)
        else:
            if not (0 <= prefix <= 128):
                raise ValueError("Prefixs for IPv6 must be in the range "
                                 "0 <= val <= 128.  Got %d" % prefix)
            if prefix == 0:
                mask = 0
            else:
                mask = ~0 << (128 - prefix)

        self.prefix = prefix
        self._mask = mask


    def __eq__(self, other):
        """Overload the == operator"""

        if other.__class__ != self.__class__:
            try:
                other = CIDR(other)
            except Exception:
                return NotImplemented

        if self._version != other._version:
            return False
        elif self.prefix != other.prefix:
            return False
        else:
            return ((self.addr.ip & self._mask) ==
                    (other.addr.ip & other._mask))

    def __ne__(self, other):
        """Overload the != operator"""
        if other.__class__ != self.__class__:
            try:
                other = CIDR(other)
            except Exception:
                return NotImplemented

        if self._version != other._version:
            return True
        elif self.prefix != other.prefix:
            return True
        else:
            return ((self.addr.ip & self._mask) !=
                    (other.addr.ip & other._mask))
